#include "CalculationApp.h"
#include <d3d12.h>
#include <dxgiformat.h>
#include <vector>
#include "Scene/TextureLoader.h"
#include "DX12Utility.h"

inline HRESULT D3DX12SerializeVersionedRootSignature(
    _In_ const D3D12_VERSIONED_ROOT_SIGNATURE_DESC* pRootSignatureDesc,
    D3D_ROOT_SIGNATURE_VERSION MaxVersion,
    _Outptr_ ID3DBlob** ppBlob,
    _Always_(_Outptr_opt_result_maybenull_) ID3DBlob** ppErrorBlob
) noexcept
{
    if (ppErrorBlob != nullptr)
    {
        *ppErrorBlob = nullptr;
    }

    switch (MaxVersion)
    {
    case D3D_ROOT_SIGNATURE_VERSION_1_0:
        switch (pRootSignatureDesc->Version)
        {
        case D3D_ROOT_SIGNATURE_VERSION_1_0:
            return D3D12SerializeRootSignature(&pRootSignatureDesc->Desc_1_0, D3D_ROOT_SIGNATURE_VERSION_1, ppBlob, ppErrorBlob);

        case D3D_ROOT_SIGNATURE_VERSION_1_1:
        {
            HRESULT hr = S_OK;
            const D3D12_ROOT_SIGNATURE_DESC1& desc_1_1 = pRootSignatureDesc->Desc_1_1;

            const SIZE_T ParametersSize = sizeof(D3D12_ROOT_PARAMETER) * desc_1_1.NumParameters;
            void* pParameters = (ParametersSize > 0) ? HeapAlloc(GetProcessHeap(), 0, ParametersSize) : nullptr;
            if (ParametersSize > 0 && pParameters == nullptr)
            {
                hr = E_OUTOFMEMORY;
            }
            auto pParameters_1_0 = static_cast<D3D12_ROOT_PARAMETER*>(pParameters);

            if (SUCCEEDED(hr))
            {
                for (UINT n = 0; n < desc_1_1.NumParameters; n++)
                {
                    __analysis_assume(ParametersSize == sizeof(D3D12_ROOT_PARAMETER) * desc_1_1.NumParameters);
                    pParameters_1_0[n].ParameterType = desc_1_1.pParameters[n].ParameterType;
                    pParameters_1_0[n].ShaderVisibility = desc_1_1.pParameters[n].ShaderVisibility;

                    switch (desc_1_1.pParameters[n].ParameterType)
                    {
                    case D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS:
                        pParameters_1_0[n].Constants.Num32BitValues = desc_1_1.pParameters[n].Constants.Num32BitValues;
                        pParameters_1_0[n].Constants.RegisterSpace = desc_1_1.pParameters[n].Constants.RegisterSpace;
                        pParameters_1_0[n].Constants.ShaderRegister = desc_1_1.pParameters[n].Constants.ShaderRegister;
                        break;

                    case D3D12_ROOT_PARAMETER_TYPE_CBV:
                    case D3D12_ROOT_PARAMETER_TYPE_SRV:
                    case D3D12_ROOT_PARAMETER_TYPE_UAV:
                        pParameters_1_0[n].Descriptor.RegisterSpace = desc_1_1.pParameters[n].Descriptor.RegisterSpace;
                        pParameters_1_0[n].Descriptor.ShaderRegister = desc_1_1.pParameters[n].Descriptor.ShaderRegister;
                        break;

                    case D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE:
                        const D3D12_ROOT_DESCRIPTOR_TABLE1& table_1_1 = desc_1_1.pParameters[n].DescriptorTable;

                        const SIZE_T DescriptorRangesSize = sizeof(D3D12_DESCRIPTOR_RANGE) * table_1_1.NumDescriptorRanges;
                        void* pDescriptorRanges =
                            (DescriptorRangesSize > 0 && SUCCEEDED(hr)) ? HeapAlloc(GetProcessHeap(), 0, DescriptorRangesSize) : nullptr;
                        if (DescriptorRangesSize > 0 && pDescriptorRanges == nullptr)
                        {
                            hr = E_OUTOFMEMORY;
                        }
                        auto pDescriptorRanges_1_0 = static_cast<D3D12_DESCRIPTOR_RANGE*>(pDescriptorRanges);

                        if (SUCCEEDED(hr))
                        {
                            for (UINT x = 0; x < table_1_1.NumDescriptorRanges; x++)
                            {
                                __analysis_assume(DescriptorRangesSize == sizeof(D3D12_DESCRIPTOR_RANGE) * table_1_1.NumDescriptorRanges);
                                pDescriptorRanges_1_0[x].BaseShaderRegister = table_1_1.pDescriptorRanges[x].BaseShaderRegister;
                                pDescriptorRanges_1_0[x].NumDescriptors = table_1_1.pDescriptorRanges[x].NumDescriptors;
                                pDescriptorRanges_1_0[x].OffsetInDescriptorsFromTableStart =
                                    table_1_1.pDescriptorRanges[x].OffsetInDescriptorsFromTableStart;
                                pDescriptorRanges_1_0[x].RangeType = table_1_1.pDescriptorRanges[x].RangeType;
                                pDescriptorRanges_1_0[x].RegisterSpace = table_1_1.pDescriptorRanges[x].RegisterSpace;
                            }
                        }

                        D3D12_ROOT_DESCRIPTOR_TABLE& table_1_0 = pParameters_1_0[n].DescriptorTable;
                        table_1_0.NumDescriptorRanges = table_1_1.NumDescriptorRanges;
                        table_1_0.pDescriptorRanges = pDescriptorRanges_1_0;
                    }
                }
            }

            if (SUCCEEDED(hr))
            {
                D3D12_ROOT_SIGNATURE_DESC desc_1_0(
                    desc_1_1.NumParameters, pParameters_1_0, desc_1_1.NumStaticSamplers, desc_1_1.pStaticSamplers, desc_1_1.Flags
                );
                hr = D3D12SerializeRootSignature(&desc_1_0, D3D_ROOT_SIGNATURE_VERSION_1, ppBlob, ppErrorBlob);
            }

            if (pParameters)
            {
                for (UINT n = 0; n < desc_1_1.NumParameters; n++)
                {
                    if (desc_1_1.pParameters[n].ParameterType == D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE)
                    {
                        auto pDescriptorRanges_1_0 = pParameters_1_0[n].DescriptorTable.pDescriptorRanges;
                        HeapFree(GetProcessHeap(), 0, reinterpret_cast<void*>(const_cast<D3D12_DESCRIPTOR_RANGE*>(pDescriptorRanges_1_0)));
                    }
                }
                HeapFree(GetProcessHeap(), 0, pParameters);
            }
            return hr;
        }
        }
        break;

    case D3D_ROOT_SIGNATURE_VERSION_1_1:
        return D3D12SerializeVersionedRootSignature(pRootSignatureDesc, ppBlob, ppErrorBlob);
    }

    return E_INVALIDARG;
}
void CalculationApp::createResouces()
{
    { // load texture
        TextureLoader loader;
        std::string hdrPath;
        if (hdrPath.empty())
            hdrPath = "D:/projects/selahengine/Assets/HDR/qwantani_sunset_4k.hdr";
            // hdrPath = "D:/projects/selahengine/Assets/HDR/newport_loft.hdr";

        float* pData = loader.loadHDR(hdrPath.c_str(), &width, &height, &channels);

        //calculate If
        auto luminance = [](float3 color)->float
        {
            return 0.2126 * color.x() + 0.7152 * color.y() + 0.0722 * color.z();
        };
        double If = 0.0f;
        float* ptr = pData;
        for(int i=0;i<width*height;++i){
            If += double(luminance(float3(*ptr,*(ptr+1),*(ptr+2))));
            ptr += 3;
        }
        std::cout << std::setprecision(17) << If << std::endl;


        D3D12_SUBRESOURCE_DATA subresources{.pData = pData, .RowPitch = width * channels * 4, .SlicePitch = height * width * channels * 4};
        uploadResourceHDR(width, height, &inputTexture.pResource, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE, 1, &subresources);
        gpu_objects.push_back(inputTexture.pResource);

        srvDescHeapAlloc.Alloc(&inputTexture.cpuHandle, &inputTexture.gpuHandle);

        // Describe and create a SRV for the texture.
        D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = {
            .Format = DXGI_FORMAT_R32G32B32_FLOAT,
            .ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D,
            .Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING,
            .Texture2D = {.MostDetailedMip = 0, .MipLevels = 1, .PlaneSlice = 0, .ResourceMinLODClamp = 0}
        };
        device->CreateShaderResourceView(inputTexture.pResource, &srvDesc, inputTexture.cpuHandle);
    }
    { // RT
        // we render at 1/32 resolusion
        rt_width = width / 32;
        rt_height = height / 32;

        D3D12_RESOURCE_DESC rtDesc = {};
        rtDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
        rtDesc.Width = rt_width;
        rtDesc.Height = rt_height;
        rtDesc.DepthOrArraySize = 1;
        rtDesc.MipLevels = 1;
        rtDesc.Format = DXGI_FORMAT_R32G32B32A32_FLOAT;
        rtDesc.SampleDesc.Count = 1;
        rtDesc.SampleDesc.Quality = 0;
        rtDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;
        rtDesc.Flags = D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET;

        D3D12_HEAP_PROPERTIES heapProps = {};
        heapProps.Type = D3D12_HEAP_TYPE_DEFAULT;

        D3D12_CLEAR_VALUE clearValue = {};
        clearValue.Format = DXGI_FORMAT_R32G32B32A32_FLOAT;
        clearValue.Color[0] = 0.2f;
        clearValue.Color[1] = 0.2f;
        clearValue.Color[2] = 0.4f;
        clearValue.Color[3] = 1.0f;

        if (FAILED(device->CreateCommittedResource(
                &heapProps,
                D3D12_HEAP_FLAG_NONE,
                &rtDesc,
                D3D12_RESOURCE_STATE_RENDER_TARGET,
                &clearValue,
                IID_PPV_ARGS(&outputTexture.pResource)
            )))
        {
        }
        gpu_objects.push_back(outputTexture.pResource);
        D3D12_RENDER_TARGET_VIEW_DESC desc = {
            .Format = DXGI_FORMAT_R32G32B32A32_FLOAT,
            .ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2D,
            .Texture2D = {.MipSlice = 0, .PlaneSlice = 0}

        };
        outputTexture.cpuHandle = rtvDescHeap->GetCPUDescriptorHandleForHeapStart();
        device->CreateRenderTargetView(outputTexture.pResource, &desc, outputTexture.cpuHandle);

        // Get required size for readback buffer
        UINT64 requiredSize = 0;
        device->GetCopyableFootprints(&outputTexture.pResource->GetDesc(), 0, 1, 0, nullptr, nullptr, nullptr, &requiredSize);
        // readback
        //  Create readback buffer
        D3D12_RESOURCE_DESC bufferDesc = {};
        bufferDesc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
        bufferDesc.Width = requiredSize;
        bufferDesc.Height = 1;
        bufferDesc.DepthOrArraySize = 1;
        bufferDesc.MipLevels = 1;
        bufferDesc.Format = DXGI_FORMAT_UNKNOWN;
        bufferDesc.SampleDesc.Count = 1;
        bufferDesc.SampleDesc.Quality = 0;
        bufferDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
        bufferDesc.Flags = D3D12_RESOURCE_FLAG_NONE;

        heapProps.Type = D3D12_HEAP_TYPE_READBACK;

        if (FAILED(device->CreateCommittedResource(
                &heapProps,
                D3D12_HEAP_FLAG_NONE,
                &bufferDesc,
                D3D12_RESOURCE_STATE_COPY_DEST,
                nullptr,
                IID_PPV_ARGS(&readbackTexture.pResource)
            )))
        {
        }
        gpu_objects.push_back(readbackTexture.pResource);
    }
    {
        // rootsig, PSO, shader
        { // rootsig
            D3D12_STATIC_SAMPLER_DESC sampler = {};
            sampler.Filter = D3D12_FILTER_MIN_MAG_MIP_POINT;
            sampler.AddressU = D3D12_TEXTURE_ADDRESS_MODE_BORDER;
            sampler.AddressV = D3D12_TEXTURE_ADDRESS_MODE_BORDER;
            sampler.AddressW = D3D12_TEXTURE_ADDRESS_MODE_BORDER;
            sampler.MipLODBias = 0;
            sampler.MaxAnisotropy = 0;
            sampler.ComparisonFunc = D3D12_COMPARISON_FUNC_NEVER;
            sampler.BorderColor = D3D12_STATIC_BORDER_COLOR_TRANSPARENT_BLACK;
            sampler.MinLOD = 0.0f;
            sampler.MaxLOD = D3D12_FLOAT32_MAX;
            sampler.ShaderRegister = 0;
            sampler.RegisterSpace = 0;
            sampler.ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL;

            D3D12_DESCRIPTOR_RANGE1 ranges[] = {
                // input tex
                {.RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SRV,
                 .NumDescriptors = 1,
                 .BaseShaderRegister = 0,
                 .RegisterSpace = 0,
                 .Flags = D3D12_DESCRIPTOR_RANGE_FLAG_DATA_STATIC,
                 .OffsetInDescriptorsFromTableStart = D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND},
                // static sampler
                {.RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER,
                 .NumDescriptors = 1,
                 .BaseShaderRegister = 0,
                 .RegisterSpace = 0,
                 .Flags = D3D12_DESCRIPTOR_RANGE_FLAG_NONE,
                 .OffsetInDescriptorsFromTableStart = D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND},
            };
            D3D12_ROOT_PARAMETER1 rootParam[] = {
                {.ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE,
                 .DescriptorTable = {1, &ranges[0]},
                 .ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL},
            };

            D3D12_VERSIONED_ROOT_SIGNATURE_DESC rootSignatureDesc = {
                .Version = D3D_ROOT_SIGNATURE_VERSION_1_1,
                .Desc_1_1 =
                    {.NumParameters = 1,
                     .pParameters = rootParam,
                     .NumStaticSamplers = 1,
                     .pStaticSamplers = &sampler,
                     .Flags = D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT}
            };
            // signature
            ID3DBlob* signature;
            ID3DBlob* error;
            D3DX12SerializeVersionedRootSignature(&rootSignatureDesc, D3D_ROOT_SIGNATURE_VERSION_1_1, &signature, &error);
            if (error)
                std::cout << ((char*)error->GetBufferPointer()) << std::endl;
            device->CreateRootSignature(0, signature->GetBufferPointer(), signature->GetBufferSize(), IID_PPV_ARGS(&rootSig));
            signature->Release();
            if (error)
                error->Release();
        }
        {
            ID3DBlob* vertexShader = ShaderReflector::getInstance().compileShaderToObj(
                L"Shaders/generate_diffuse_irradiance_map.hlsl", ShaderReflector::ShaderType::Vertex
            );
            ID3DBlob* pixelShader = ShaderReflector::getInstance().compileShaderToObj(
                L"Shaders/generate_diffuse_irradiance_map.hlsl", ShaderReflector::ShaderType::Pixel
            );
            D3D12_SHADER_BYTECODE vs = {
                .pShaderBytecode = vertexShader->GetBufferPointer(), .BytecodeLength = vertexShader->GetBufferSize()
            };

            D3D12_SHADER_BYTECODE ps = {.pShaderBytecode = pixelShader->GetBufferPointer(), .BytecodeLength = pixelShader->GetBufferSize()};

            D3D12_RASTERIZER_DESC rasterizerDesc = {
                .FillMode = D3D12_FILL_MODE_SOLID,
                .CullMode = D3D12_CULL_MODE_BACK,
                .FrontCounterClockwise = TRUE,
                .DepthBias = D3D12_DEFAULT_DEPTH_BIAS,
                .DepthBiasClamp = D3D12_DEFAULT_DEPTH_BIAS_CLAMP,
                .SlopeScaledDepthBias = D3D12_DEFAULT_SLOPE_SCALED_DEPTH_BIAS,
                .DepthClipEnable = TRUE,
                .MultisampleEnable = FALSE,
                .AntialiasedLineEnable = FALSE,
                .ForcedSampleCount = 0,
                .ConservativeRaster = D3D12_CONSERVATIVE_RASTERIZATION_MODE_OFF
            };

            D3D12_BLEND_DESC blendDesc = {.AlphaToCoverageEnable = FALSE, .IndependentBlendEnable = FALSE};
            const D3D12_RENDER_TARGET_BLEND_DESC defaultRenderTargetBlendDesc = {
                FALSE,
                FALSE,
                D3D12_BLEND_ONE,
                D3D12_BLEND_ZERO,
                D3D12_BLEND_OP_ADD,
                D3D12_BLEND_ONE,
                D3D12_BLEND_ZERO,
                D3D12_BLEND_OP_ADD,
                D3D12_LOGIC_OP_NOOP,
                D3D12_COLOR_WRITE_ENABLE_ALL,
            };
            for (UINT i = 0; i < D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT; ++i)
                blendDesc.RenderTarget[i] = defaultRenderTargetBlendDesc;

            D3D12_DEPTH_STENCIL_DESC depthStencilDesc = {
                .DepthEnable = FALSE,
                .DepthWriteMask = D3D12_DEPTH_WRITE_MASK_ZERO,
                .DepthFunc = D3D12_COMPARISON_FUNC_ALWAYS,
                .StencilEnable = FALSE,
            };

            // Describe and create the graphics pipeline state object (PSO).
            D3D12_GRAPHICS_PIPELINE_STATE_DESC psoDesc = {};
            psoDesc.InputLayout = {};
            psoDesc.pRootSignature = rootSig;
            psoDesc.VS = vs;
            psoDesc.PS = ps;
            psoDesc.RasterizerState = rasterizerDesc;
            psoDesc.BlendState = blendDesc;
            psoDesc.DepthStencilState = depthStencilDesc;
            psoDesc.SampleMask = UINT_MAX;
            psoDesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
            psoDesc.NumRenderTargets = 1;
            psoDesc.RTVFormats[0] = DXGI_FORMAT_R32G32B32A32_FLOAT;
            psoDesc.SampleDesc.Count = 1;
            HRESULT hr = device->CreateGraphicsPipelineState(&psoDesc, IID_PPV_ARGS(&pso));
            if (hr != S_OK)
            {
                std::cout << "Failed to create graphics pipeline state: " << std::hex << hr << std::endl;
            }
            vertexShader->Release();
            pixelShader->Release();
        }

        gpu_objects.push_back(pso);
        gpu_objects.push_back(rootSig);
    }
}

void CalculationApp::calculate()
{
    cmdAllocDirect->Reset();
    cmdListDirect->Reset(cmdAllocDirect, pso);

    D3D12_VIEWPORT viewport = {0.0f, 0.0f, (float)rt_width, (float)rt_height, 0.0f, 1.0f};
    D3D12_RECT scissorRect = {0, 0, (long)rt_width, (long)rt_height};
    cmdListDirect->RSSetViewports(1, &viewport);
    cmdListDirect->RSSetScissorRects(1, &scissorRect);

    // render skybox
    cmdListDirect->SetGraphicsRootSignature(rootSig);
    cmdListDirect->SetPipelineState(pso);
    cmdListDirect->SetDescriptorHeaps(1, &srvDescHeap);
    cmdListDirect->SetGraphicsRootDescriptorTable(0, inputTexture.gpuHandle);
    cmdListDirect->OMSetRenderTargets(1, &outputTexture.cpuHandle, false, nullptr);
    cmdListDirect->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
    cmdListDirect->DrawInstanced(3, 1, 0, 0);

    // Transition render target for copy
    D3D12_RESOURCE_BARRIER barrier = {};
    barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
    barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
    barrier.Transition.pResource = outputTexture.pResource;
    barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET;
    barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_COPY_SOURCE;
    barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
    cmdListDirect->ResourceBarrier(1, &barrier);

    // Copy render target to readback buffer
    D3D12_TEXTURE_COPY_LOCATION srcLocation = {};
    srcLocation.pResource = outputTexture.pResource;
    srcLocation.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
    srcLocation.SubresourceIndex = 0;

    D3D12_TEXTURE_COPY_LOCATION dstLocation = {};
    dstLocation.pResource = readbackTexture.pResource;
    dstLocation.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT;

    UINT64 requiredSize = 0;
    D3D12_PLACED_SUBRESOURCE_FOOTPRINT footprint = {};
    UINT numRows = 0;
    UINT64 rowSizeInBytes = 0;

    device->GetCopyableFootprints(&outputTexture.pResource->GetDesc(), 0, 1, 0, &footprint, &numRows, &rowSizeInBytes, &requiredSize);

    dstLocation.PlacedFootprint = footprint;

    cmdListDirect->CopyTextureRegion(&dstLocation, 0, 0, 0, &srcLocation, nullptr);

    cmdListDirect->Close();

    ID3D12CommandList* commandLists[] = {cmdListDirect};
    directQueue->ExecuteCommandLists(1, commandLists);

    waitForGpu();
}

void CalculationApp::getResults()
{
    // Map readback buffer
    void* mappedData = nullptr;
    D3D12_RANGE readRange = {0, 0};

    HRESULT hr = readbackTexture.pResource->Map(0, &readRange, &mappedData);

    // Get footprint information
    D3D12_PLACED_SUBRESOURCE_FOOTPRINT footprint = {};
    UINT numRows = 0;
    UINT64 rowSizeInBytes = 0;
    UINT64 requiredSize = 0;

    device->GetCopyableFootprints(&outputTexture.pResource->GetDesc(), 0, 1, 0, &footprint, &numRows, &rowSizeInBytes, &requiredSize);

    // Copy pixel data
    std::vector<uint8_t> pixels;
    const int pixelBytes = 16;
    pixels.resize(rt_width * rt_height * pixelBytes);

    uint8_t* src = static_cast<uint8_t*>(mappedData);
    uint8_t* dst = pixels.data();

    for (UINT row = 0; row < rt_height; ++row)
    {
        memcpy(dst, src, rt_width * pixelBytes);
        src += footprint.Footprint.RowPitch;
        dst += rt_width * pixelBytes;
    }

    // Unmap buffer
    readbackTexture.pResource->Unmap(0, nullptr);

    TextureLoader loader;
    loader.storeHDR("irradiance_map.hdr", rt_width, rt_height, 4, reinterpret_cast<float*>(pixels.data()));
}
void CalculationApp::run()
{
    initialize();
    createResouces();
    calculate();
    getResults();
    waitForGpu();
    shutDown();
}


void CalculationApp::initialize()
{
    if (initialized || !initializeBackend())
    {
        return;
    }

    initialized = true;
}

bool CalculationApp::initializeBackend()
{
    // [DEBUG] Enable debug interface
#ifdef DX12_ENABLE_DEBUG_LAYER
    ID3D12Debug* pdx12Debug = nullptr;
    if (SUCCEEDED(D3D12GetDebugInterface(IID_PPV_ARGS(&pdx12Debug))))
        pdx12Debug->EnableDebugLayer();
#endif

    // Create device
    D3D_FEATURE_LEVEL featureLevel = D3D_FEATURE_LEVEL_11_0;
    if (D3D12CreateDevice(nullptr, featureLevel, IID_PPV_ARGS(&device)) != S_OK)
        return false;
    gpu_objects.push_back(device);

    // [DEBUG] Setup debug interface to break on any warnings/errors
#ifdef DX12_ENABLE_DEBUG_LAYER
    if (pdx12Debug != nullptr)
    {
        ID3D12InfoQueue* pInfoQueue = nullptr;
        device->QueryInterface(IID_PPV_ARGS(&pInfoQueue));
        pInfoQueue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_ERROR, true);
        pInfoQueue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_CORRUPTION, true);
        // pInfoQueue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_WARNING, true);
        pInfoQueue->Release();
        pdx12Debug->Release();
    }
#endif
    // descriptor heaps
    {
        // rtv
        D3D12_DESCRIPTOR_HEAP_DESC rtvHeapDesc = {
            .Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV,
            .NumDescriptors = APP_NUM_BACK_BUFFERS * 2,
            .Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE,
            .NodeMask = 1 // ? dont what this mean
        };
        if (device->CreateDescriptorHeap(&rtvHeapDesc, IID_PPV_ARGS(&rtvDescHeap)) != S_OK)
            return false;

        // srv
        D3D12_DESCRIPTOR_HEAP_DESC srvHeapdesc = {
            .Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV,
            .NumDescriptors = APP_SRV_HEAP_SIZE,
            .Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE
        };
        if (device->CreateDescriptorHeap(&srvHeapdesc, IID_PPV_ARGS(&srvDescHeap)) != S_OK)
            return false;

        srvDescHeapAlloc.Create(device, srvDescHeap);

        gpu_objects.push_back(srvDescHeap);
        gpu_objects.push_back(rtvDescHeap);
    }

    // queue
    {
        D3D12_COMMAND_QUEUE_DESC desc = {};
        desc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
        desc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
        desc.NodeMask = 1;
        if (device->CreateCommandQueue(&desc, IID_PPV_ARGS(&directQueue)) != S_OK)
            return false;

        desc.Type = D3D12_COMMAND_LIST_TYPE_COPY;
        desc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
        desc.NodeMask = 1;
        if (device->CreateCommandQueue(&desc, IID_PPV_ARGS(&copyQueue)) != S_OK)
            return false;
        gpu_objects.push_back(copyQueue);
        gpu_objects.push_back(directQueue);
    }

    { // command allocator
        if (device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&cmdAllocDirect)) != S_OK)
            return false;
        if (device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&cmdAllocCopy)) != S_OK)
            return false;

        gpu_objects.push_back(cmdAllocCopy);
        gpu_objects.push_back(cmdAllocDirect);
    }
    { // command list
        if (device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, cmdAllocDirect, nullptr, IID_PPV_ARGS(&cmdListDirect)) != S_OK)
            return false;
        if (device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, cmdAllocCopy, nullptr, IID_PPV_ARGS(&cmdListCopy)) != S_OK)
            return false;
        cmdListCopy->Close();
        cmdListDirect->Close();
        gpu_objects.push_back(cmdListCopy);
        gpu_objects.push_back(cmdListDirect);
    }
    { // fence
        if (device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&fence)) != S_OK)
            return false;

        gpu_objects.push_back(fence);
    }
    return true;
}

void CalculationApp::shutDown()
{
    shutDownBackend();

#ifdef DX12_ENABLE_DEBUG_LAYER
    IDXGIDebug1* pDebug = nullptr;
    if (SUCCEEDED(DXGIGetDebugInterface1(0, IID_PPV_ARGS(&pDebug))))
    {
        pDebug->ReportLiveObjects(DXGI_DEBUG_ALL, DXGI_DEBUG_RLO_ALL);
        pDebug->Release();
    }
#endif
}

void CalculationApp::shutDownBackend()
{
    while (!gpu_objects.empty())
    {
        gpu_objects.back()->Release();
        gpu_objects.pop_back();
    }
}

void CalculationApp::waitForGpu()
{
    directQueue->Signal(fence, fenceValues);

    // Wait until the fence has been processed.
    fence->SetEventOnCompletion(fenceValues, fenceEvent);
    WaitForSingleObjectEx(fenceEvent, INFINITE, FALSE);

    // Increment the fence value for the current frame.
    fenceValues++;
}

void CalculationApp::moveToNextFrame()
{
    // Schedule a Signal command in the queue.
    const UINT64 currentFenceValue = fenceValues;
    directQueue->Signal(fence, currentFenceValue);

    // If the next frame is not ready to be rendered yet, wait until it is ready.
    if (fence->GetCompletedValue() < fenceValues)
    {
        fence->SetEventOnCompletion(fenceValues, fenceEvent);
        WaitForSingleObjectEx(fenceEvent, INFINITE, FALSE);
    }

    // Set the fence value for the next frame.
    fenceValues = currentFenceValue + 1;
}
void CalculationApp::uploadResourceTexture(
    UINT width,
    UINT height,
    ID3D12Resource** ppResource,
    D3D12_RESOURCE_STATES afterState,
    UINT NumSubresources,
    const D3D12_SUBRESOURCE_DATA* pSrcData
)
{
    // Create GPU buffer
    D3D12_HEAP_PROPERTIES heapProps = {
        .Type = D3D12_HEAP_TYPE_DEFAULT,
        .CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN,
        .MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN,
        .CreationNodeMask = 1,
        .VisibleNodeMask = 1
    };
    D3D12_RESOURCE_DESC resourceDesc = {
        .Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D,
        .Alignment = 0,
        .Width = width,
        .Height = height,
        .DepthOrArraySize = (UINT16)NumSubresources,
        .MipLevels = 1,
        .Format = DXGI_FORMAT_R8G8B8A8_UNORM,
        .SampleDesc = {1, 0},
        .Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN,
        .Flags = D3D12_RESOURCE_FLAG_NONE
    };

    device->CreateCommittedResource(
        &heapProps, D3D12_HEAP_FLAG_NONE, &resourceDesc, D3D12_RESOURCE_STATE_COPY_DEST, nullptr, IID_PPV_ARGS(ppResource)
    );
    const UINT64 uploadBufferSize = GetRequiredIntermediateSize(*ppResource, 0, NumSubresources);

    // Create upload buffer
    heapProps.Type = D3D12_HEAP_TYPE_UPLOAD;
    resourceDesc = {
        .Dimension = D3D12_RESOURCE_DIMENSION_BUFFER,
        .Alignment = 0,
        .Width = uploadBufferSize,
        .Height = 1,
        .DepthOrArraySize = 1,
        .MipLevels = 1,
        .Format = DXGI_FORMAT_UNKNOWN,
        .SampleDesc = {1, 0},
        .Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR,
        .Flags = D3D12_RESOURCE_FLAG_NONE
    };

    device->CreateCommittedResource(
        &heapProps, D3D12_HEAP_FLAG_NONE, &resourceDesc, D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, IID_PPV_ARGS(&uploadBuffer)
    );

    cmdListDirect->Reset(cmdAllocDirect, nullptr);

    UpdateSubresources(cmdListDirect, *ppResource, uploadBuffer, 0, 0, NumSubresources, pSrcData);

    // Transition to vertex buffer
    D3D12_RESOURCE_BARRIER barrier = {
        .Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION,
        .Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE,
        .Transition =
            {.pResource = *ppResource,
             .Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES,
             .StateBefore = D3D12_RESOURCE_STATE_COPY_DEST,
             .StateAfter = afterState},
    };

    cmdListDirect->ResourceBarrier(1, &barrier);

    cmdListDirect->Close();
    ID3D12CommandList* ppCommandLists[] = {cmdListDirect};
    directQueue->ExecuteCommandLists(1, ppCommandLists);

    waitForGpu();

    uploadBuffer->Release();
    uploadBuffer = nullptr;
}

void CalculationApp::uploadResourceHDR(
    UINT width,
    UINT height,
    ID3D12Resource** ppResource,
    D3D12_RESOURCE_STATES afterState,
    UINT NumSubresources,
    const D3D12_SUBRESOURCE_DATA* pSrcData
)
{
    // Create GPU buffer
    D3D12_HEAP_PROPERTIES heapProps = {
        .Type = D3D12_HEAP_TYPE_DEFAULT,
        .CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN,
        .MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN,
        .CreationNodeMask = 1,
        .VisibleNodeMask = 1
    };
    D3D12_RESOURCE_DESC resourceDesc = {
        .Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D,
        .Alignment = 0,
        .Width = width,
        .Height = height,
        .DepthOrArraySize = (UINT16)NumSubresources,
        .MipLevels = 1,
        .Format = DXGI_FORMAT_R32G32B32_FLOAT,
        .SampleDesc = {1, 0},
        .Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN,
        .Flags = D3D12_RESOURCE_FLAG_NONE
    };

    device->CreateCommittedResource(
        &heapProps, D3D12_HEAP_FLAG_NONE, &resourceDesc, D3D12_RESOURCE_STATE_COPY_DEST, nullptr, IID_PPV_ARGS(ppResource)
    );
    const UINT64 uploadBufferSize = GetRequiredIntermediateSize(*ppResource, 0, NumSubresources);

    // Create upload buffer
    heapProps.Type = D3D12_HEAP_TYPE_UPLOAD;
    resourceDesc = {
        .Dimension = D3D12_RESOURCE_DIMENSION_BUFFER,
        .Alignment = 0,
        .Width = uploadBufferSize,
        .Height = 1,
        .DepthOrArraySize = 1,
        .MipLevels = 1,
        .Format = DXGI_FORMAT_UNKNOWN,
        .SampleDesc = {1, 0},
        .Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR,
        .Flags = D3D12_RESOURCE_FLAG_NONE
    };

    device->CreateCommittedResource(
        &heapProps, D3D12_HEAP_FLAG_NONE, &resourceDesc, D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, IID_PPV_ARGS(&uploadBuffer)
    );

    cmdListDirect->Reset(cmdAllocDirect, nullptr);

    UpdateSubresources(cmdListDirect, *ppResource, uploadBuffer, 0, 0, NumSubresources, pSrcData);

    D3D12_RESOURCE_BARRIER barrier = {
        .Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION,
        .Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE,
        .Transition =
            {.pResource = *ppResource,
             .Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES,
             .StateBefore = D3D12_RESOURCE_STATE_COPY_DEST,
             .StateAfter = afterState},
    };

    cmdListDirect->ResourceBarrier(1, &barrier);

    cmdListDirect->Close();
    ID3D12CommandList* ppCommandLists[] = {cmdListDirect};
    directQueue->ExecuteCommandLists(1, ppCommandLists);

    waitForGpu();

    uploadBuffer->Release();
    uploadBuffer = nullptr;
}